<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>设计模式手册</title>
</head>
<body>
    <h1>设计模式 javascript (24种)</h1>
    <pre>
        设计模式简介
            设计模式的理念基于面向对象思想, 是前辈们在软件开发过程中面临一般问题的解决方案
            这些方案是众多开发人员经过相当长一段时间的经验总结

            使用设计模式, 是为了重用代码. 让代码更容易理解, 保证代码的可靠性

            设计模式, 使代码真正的工程化, 设计模式是软件工程的基石, 
            目前市面上大部分程序框架都是由设计模式套出来的, 所以这是解决问题的通用思路

        设计模式的五大原则: SOLID

        S - Single Responsibility   单一职责原则
        O - OpenClosed              开放封闭原则
        L - Liskov Substitution     李氏替换原则
        I - Interface Segregation   接口独立原则
        D - Dependency Inversion    依赖倒转原则

            1. 单一职责原则
                一个程序, 只做一件事
                功能如果过于复杂, 就拆分开. 每个部分保持独立
                (在开发中有几条不成文的规定
                    一个文件不能超过300行,
                    一行代码不能超过80列,
                    一个函数不能超过50行
                )

            2. 开放封闭原则
                对扩展开放, 对修改封闭
                增加需求时, 扩展新代码, 而不是修改已有代码
            
            3. 李氏替换原则
                子类在任何情况下都可以替换父类
                父类能出现的地方, 子类就能出现
                js中使用比较少(弱类型&继承使用的少)

            4. 接口独立原则
                保持接口的单一独立, 避免出现"胖接口"

            5. 依赖倒转原则
                不依赖具体某个对象, 而是依赖某个更加抽象的类或者接口
    </pre>
    <h2>4 设计模式分类（24种设计模式）</h2>

    <ul>
        <li>
            <h3>创建型</h3>
            <ol>
                <li><a href="#单例模式">单例模式</a></li>
                <li><a href="#原型模式">原型模式</a></li>
                <li><a href="#工厂模式">工厂模式</a></li>
                <li><a href="#抽象工厂模式">抽象工厂模式</a></li>
                <li><a href="#建造者模式">建造者模式</a></li>
            </ol>
        </li>
        <li>
            <h3>结构型</h3>
            <ol>
                <li><a href="#适配器模式">适配器模式</a></li>
                <li><a href="#装饰器模式">装饰器模式</a></li>
                <li><a href="#代理模式">代理模式</a></li>
                <li><a href="#外观模式">外观模式</a></li>
                <li><a href="#桥接模式">桥接模式</a></li>
                <li><a href="#组合模式">组合模式</a></li>
                <li><a href="#享元模式">享元模式</a></li>
            </ol>
        </li>
        <li>
            <h3>行为型</h3>
            <ol>
                <li><a href="#观察者模式">观察者模式</a></li>
                <li><a href="#订阅发布模式">订阅发布模式</a></li>
                <li><a href="#迭代器模式">迭代器模式</a></li>
                <li><a href="#策略模式">策略模式</a></li>
                <li><a href="#模板方法模式">模板方法模式</a></li>
                <li><a href="#职责链模式">职责链模式</a></li>
                <li><a href="#命令模式">命令模式</a></li>
                <li><a href="#备忘录模式">备忘录模式</a></li>
                <li><a href="#状态模式">状态模式</a></li>
                <li><a href="#访问者模式">访问者模式</a></li>
                <li><a href="#中介者模式">中介者模式</a></li>
                <li><a href="#解释器模式">解释器模式</a></li>
            </ol>
        </li>
    </ul>

    <h3 id="单例模式">1. 单例模式</h3>
    <pre>
        一个类只有一个实例, 并提供一个全局的访问点
        
        使用场景: 
            1. 登录弹窗
            2. redux 状态树

        优点:
            1. 隐藏命名空间(闭包)
            2. 只会实例化一次, 简化代码调试和维护
        缺点:
            1. 不利于测试, 因为他会导致模块之间的强耦合, 
            2. 测试的时候必须要构建一次单例

        class LgoinForm{
            constructor() {
                this.state = 'hide'
            }
        }

        LgoinForm.getInstance = (function() {
            let instance = null
            return function() {
                if (!instance) {
                    instance = new LgoinForm()
                }
                return instance
            }
        })()

        let l1 = LgoinForm.getInstance()
        let l2 = LgoinForm.getInstance()
        console.log(l1 === l2);

        <script>
            (() => {
                console.log('1. 单例模式');
                class LgoinForm{
                    constructor() {
                        this.state = 'hide'
                    }
                }

                LgoinForm.getInstance = (function() {
                    let instance = null
                    return function() {
                        if (!instance) {
                            instance = new LgoinForm()
                        }
                        return instance
                    }
                })()

                let l1 = LgoinForm.getInstance()
                let l2 = LgoinForm.getInstance()
                console.log(l1 === l2);
            })()
        </script>
    </pre>

    <h3 id="原型模式">2. 原型模式</h3>
    <pre>
        这创建对象的另一种方法, 他是去克隆一个对象. 
        这样做的好处是他可以完整的复制当前对象的状态
        (很多时候 对象都有初始状态和各种业务类型的中间状态)
        
        使用场景: 
            复制现有实例
        优点:
            1. 节省对象创建时的开销
        缺点:
            1. 扩展能力差, 创建好对象后还要对其修改

        let person = {
            name: 'xinglong',
            sayHi: function() {
                console.log(`${this.name} - Hello`);
            },
            setName(name) {
                this.name = name
            }
        }

        person.sayHi()

        // let p1 = {
        //     ...person,
        // }
        let p1 = Object.create(person, {})
        // let p1 = {}
        // for (let key in person) {
        //     p1[key] = person[key]
        // }

        p1.name = '张国荣'
        p1.sayHi()


        <script>
            (() => {
                console.log('2. 原型模式');
                let person = {
                    name: 'xinglong',
                    sayHi: function() {
                        console.log(`${this.name} - Hello`);
                    },
                    setName(name) {
                        this.name = name
                    }
                }

                person.sayHi()

                // let p1 = {
                //     ...person,
                // }
                let p1 = Object.create(person, {})
                // let p1 = {}
                // for (let key in person) {
                //     p1[key] = person[key]
                // }

                p1.name = '张国荣'
                p1.sayHi()

            })()
        </script>
    </pre>

    <h3 id="工厂模式">3. 工厂模式</h3>
    <pre>
        
        封装了 new 的动作, 接受一个指令帮你创建相应实例

        使用场景: 
            1. 工厂生产裙子, 客户下单只需要一个代号

        优点:
            1. 创建对象的过程可能很复杂, 但是我们只用关注创建结果
            2. 构造函数和创建者分离, 符合“开闭原则”
            3. 一个调用者想创建一个对象，只要知道其名称就可以了。
            4. 扩展性高，如果想增加一个奖品，只要扩展一个工厂类就可以。
        缺点:
            1. 添加新产品的同时, 还要扩展工厂类.

        const skirtConf = {
            '百褶裙': {
                name: '百褶裙',
                color: '暗红色',
                price: 200,
            },
            '商务裙': {
                name: '商务裙',
                color: '黑色',
                price: 300,
            },
        }
        class Skirt { // 裙子
            constructor(type) {
                this.info = {
                    ...skirtConf[type]
                }
            }
            init() {
                console.log(this.info.name);
                console.log(this.info.color);
                console.log(this.info.price);
            }
        }

        class Factory {
            create(code) {
                return new Skirt(code)
            }
        }

        let f = new Factory()
        let p = f.create('商务裙')
        p.init()

        <script>
            (() => {
                console.log('3. 工厂模式')

                const skirtConf = {
                    '百褶裙': {
                        name: '百褶裙',
                        color: '暗红色',
                        price: 200,
                    },
                    '商务裙': {
                        name: '商务裙',
                        color: '黑色',
                        price: 300,
                    },
                }
                class Skirt { // 裙子
                    constructor(type) {
                        this.info = {
                            ...skirtConf[type]
                        }
                    }
                    init() {
                        console.log(this.info.name);
                        console.log(this.info.color);
                        console.log(this.info.price);
                    }
                }

                class Factory {
                    create(code) {
                        return new Skirt(code)
                    }
                }

                let f = new Factory()
                let p = f.create('商务裙')
                p.init()
            })()
        </script>
    </pre>

    <h3 id="抽象工厂模式">4. 抽象工厂模式</h3>
    <pre>
        创建工厂的工厂

        使用场景: 
            1. 一次性生成女生的套装
            2. 肯德基的各种套餐(豪华午餐)
        优点:
            一次性构建任意产品组
            增加代码的层次感, 简化业务逻辑实现
        缺点:
            当某个产品组新增实体时, 相应的工厂和抽象工厂都要加代码

        const clothesConf = {
            '衬衣': {name: '衬衣', color: '白色', price: 200},
        }
        const shoesConf = {
            '皮鞋': {name: '皮鞋', color: '黑色', price: 200},
            '高跟鞋': {name: '高跟鞋', color: '黑色', price: 200},
        }
        const skirtConf = {
            '百褶裙': { 
                name: '百褶裙', color: '暗红色', price: 200,
            },
            '商务裙': {
                name: '商务裙', color: '黑色', price: 300,
            },
        }

        class Clothes{ // 衣服
            constructor(type) {
                this.type = type
                this.init()
            }
            init() {
                // console.log(clothesConf[this.type]);
            }
        }

        class Shoes{ // 鞋子
            constructor(type) {
                this.type = type
                this.init()
            }
            init() {
                // console.log(shoesConf[this.type]);
            }
        }

        class Skirt{ // 裙子
            constructor(type) {
                this.type = type
                this.init()
            }
            init() {
                // console.log(shoesConf[this.type]);
            }
        }

        class JkFactory { // Jk 制服
            constructor(type) {
                this.type = type
            }

            create() {
                return [
                    new Shoes('皮鞋'),
                    new Skirt('百褶裙'),
                    new Clothes('衬衣'),
                ]
            }
        }

        class BusinessFactory { // Jk 制服
            constructor(type) {
                this.type = type
            }

            create() {
                return [
                    new Shoes('高跟鞋'),
                    new Skirt('商务裙'),
                    new Clothes('衬衣'),
                ]
            }
        }

        // 抽象工厂
        class AbstractFactory{
            constructor(type) {
                this.type = type
            }
            create() {
                if (this.type === '商务') {
                    return new BusinessFactory()
                } else if (this.type === 'jk') {
                    return new JkFactory()
                }
            }
        }

        let f1 = new AbstractFactory('商务').create()
        console.log(
            f1.create()
        );


        <script>
            console.log('4. 抽象工厂模式');
            (() => {
                // 构建不同的女生套装
                /*
                    鞋子: 皮鞋, 高跟鞋
                    裤子: 百褶裙, 包臀裙
                    上一: 衬衣
                */

                const clothesConf = {
                    '衬衣': {name: '衬衣', color: '白色', price: 200},
                }
                const shoesConf = {
                    '皮鞋': {name: '皮鞋', color: '黑色', price: 200},
                    '高跟鞋': {name: '高跟鞋', color: '黑色', price: 200},
                }
                const skirtConf = {
                    '百褶裙': { 
                        name: '百褶裙', color: '暗红色', price: 200,
                    },
                    '商务裙': {
                        name: '商务裙', color: '黑色', price: 300,
                    },
                }

                class Clothes{ // 衣服
                    constructor(type) {
                        this.type = type
                        this.init()
                    }
                    init() {
                        // console.log(clothesConf[this.type]);
                    }
                }

                class Shoes{ // 鞋子
                    constructor(type) {
                        this.type = type
                        this.init()
                    }
                    init() {
                        // console.log(shoesConf[this.type]);
                    }
                }

                class Skirt{ // 裙子
                    constructor(type) {
                        this.type = type
                        this.init()
                    }
                    init() {
                        // console.log(shoesConf[this.type]);
                    }
                }

                class JkFactory { // Jk 制服
                    constructor(type) {
                        this.type = type
                    }

                    create() {
                        return [
                            new Shoes('皮鞋'),
                            new Skirt('百褶裙'),
                            new Clothes('衬衣'),
                        ]
                    }
                }

                class BusinessFactory { // Jk 制服
                    constructor(type) {
                        this.type = type
                    }

                    create() {
                        return [
                            new Shoes('高跟鞋'),
                            new Skirt('商务裙'),
                            new Clothes('衬衣'),
                        ]
                    }
                }

                // 抽象工厂
                class AbstractFactory{
                    constructor(type) {
                        this.type = type
                    }
                    create() {
                        if (this.type === '商务') {
                            return new BusinessFactory()
                        } else if (this.type === 'jk') {
                            return new JkFactory()
                        }
                    }
                }

                let f1 = new AbstractFactory('商务').create()
                console.log(
                    f1.create()
                );
            })()

        </script>
    </pre>

    <h3 id="建造者模式">5. 建造者模式</h3>
    <pre>
        抽象工厂: 抽象工厂 -> 工厂 -> 具体的类(汉堡, 鸡翅)
        建造者: 建造者 -> 具体的类(汉堡, 鸡翅)

        建造者模式是把简单的对象组装成复杂对象, 有点类似与工厂模式
        但区别是, 工厂模式侧重于如何 new 对象, 

        使用场景: 
            1. 肯德基套餐, 肯德基有薯条, 汉堡, 鸡翅,可乐, 
            这四种食品可以组合成不同价格的套餐
        优点:
            1. 建造者独立, 容易扩展
            2. 方便控制细节
        缺点:
            1. 内部变化复杂, 会有很多建造者类

        class Cola{
            constructor() {
                console.log('可乐');
            }
        }
        class Chicken {
            constructor() {
                console.log('鸡翅');
            }
        }
        class Hamburger{
            constructor() {
                console.log('汉堡');
            }
        }
        class FrenchFries{
            constructor() {
                console.log('薯条');
            }
        }

        class Item { // 套餐
            constructor(name, arr) {
                this.name = name
                this.arr = arr
            }
        }

        let breakfast = new Item('早餐',[new Hamburger()])
        let lunch = new Item('午餐',[new Hamburger(), new Chicken(), new Cola()])

        class Menu{
            constructor() {
                this.menuMap = {
                    "早餐": breakfast,
                    "豪华午餐": lunch,
                }
            }
            create(name) {
                return this.menuMap[name]
            }
        }

        let m = new Menu()
        console.log(
            m.create('早餐'),
            m.create('豪华午餐')
        );

        <script>
            (() => {
                console.log('5. 建造者模式');
                class Cola{
                    constructor() {
                        console.log('可乐');
                    }
                }
                class Chicken {
                    constructor() {
                        console.log('鸡翅');
                    }
                }
                class Hamburger{
                    constructor() {
                        console.log('汉堡');
                    }
                }
                class FrenchFries{
                    constructor() {
                        console.log('薯条');
                    }
                }

                class Item { // 套餐
                    constructor(name, arr) {
                        this.name = name
                        this.arr = arr
                    }
                }

                let breakfast = new Item('早餐',[new Hamburger()])
                let lunch = new Item('午餐',[new Hamburger(), new Chicken(), new Cola()])

                class Menu{
                    constructor() {
                        this.menuMap = {
                            "早餐": breakfast,
                            "豪华午餐": lunch,
                        }
                    }
                    create(name) {
                        return this.menuMap[name]
                    }
                }

                let m = new Menu()
                console.log(
                    m.create('早餐'),
                    m.create('豪华午餐')
                );
            })()

        </script>
    </pre>

    <h3 id="适配器模式">6. 适配器模式</h3>
    <pre>
        
        可以让两个不兼容的类, 通过适配器来兼容

        使用场景:
            1. 接口转换
            2. babel

        优点:
            1. 可以让两个互相不兼容的类, 在一起运行
            2. 提高类的服用
            3. 增加类的透明度
            4. 增加灵活性
        缺点:
            1. 增加系统复杂度

        console.log('6. 适配器模式')
        let oldApi = {
            userInfo: '/user_info',
            userList: '/users',
            userUpdate: '/updateUserInfo',
        }
        let AdapterApi = {
            userInfo: 'GET /user',
            userList: 'GET /users',
            userUpdate: 'POST /user',
        }

        class Adapter {
            to(url) {
                for (let key in oldApi) {
                    if (AdapterApi[key] === url) {
                        return oldApi[key]
                    }
                }
            }
        }

        let apiLoader = new Adapter()
        console.log(
            'GET /user -> ', apiLoader.to('GET /user')
        );
        console.log(
            'POST /user -> ', apiLoader.to('POST /user')
        );

        <script>
            (() => {
                console.log('6. 适配器模式')
                let oldApi = {
                    userInfo: '/user_info',
                    userList: '/users',
                    userUpdate: '/updateUserInfo',
                }
                let AdapterApi = {
                    userInfo: 'GET /user',
                    userList: 'GET /users',
                    userUpdate: 'POST /user',
                }

                class Adapter {
                    to(url) {
                        for (let key in oldApi) {
                            if (AdapterApi[key] === url) {
                                return oldApi[key]
                            }
                        }
                    }
                }

                let apiLoader = new Adapter()
                console.log(
                    'GET /user -> ', apiLoader.to('GET /user')
                );
                console.log(
                    'POST /user -> ', apiLoader.to('POST /user')
                );
            })()
        </script>
    </pre>

    <h3 id="装饰器模式">7. 装饰器模式</h3>
    <pre>
        
        给某个对象包一层函数, 实现功能扩展但又不修改原有代码

        使用场景: 
            1. 动态增加功能
            2. react-redux 的 connect 函数
            3. 动态撤销
        优点:
            1. 不修改原有代码, 增加功能
        缺点:
            1. 多层装饰器会比较复杂

        let user1 = {
            isVip: true,
        }
        let user2 = {
            isVip: false
        }

        class UploadFile{
            constructor() {
                this.maxBandWidth = 1024
            }
            upload() {
                console.log('upload ... max - ', this.maxBandWidth);
            }
        }

        function VipUpload (u) { // 装饰器
            u.maxBandWidth = 2048
            return u
        }

        const postFile = (user) => {
            let u = null
            if (user.isVip === true) {
                u = VipUpload(new UploadFile())
            } else {
                u = new UploadFile()
            }
            u.upload()
        }
        postFile(user1)
        postFile(user2)


        <script>
            (() => {
                /*
                    上传文件
                    如果是 vip 上传速度快
                */
                console.log('7. 装饰器模式');
                let user1 = {
                    isVip: true,
                }
                let user2 = {
                    isVip: false
                }

                class UploadFile{
                    constructor() {
                        this.maxBandWidth = 1024
                    }
                    upload() {
                        console.log('upload ... max - ', this.maxBandWidth);
                    }
                }

                function VipUpload (u) { // 装饰器
                    u.maxBandWidth = 2048
                    return u
                }

                const postFile = (user) => {
                    let u = null
                    if (user.isVip === true) {
                        u = VipUpload(new UploadFile())
                    } else {
                        u = new UploadFile()
                    }
                    u.upload()
                }
                postFile(user1)
                postFile(user2)
            })()
        </script>
    </pre>

    <h3 id="代理模式">8. 代理模式</h3>
    <pre>
        
        代替某个类的行为

        使用场景: 
            1. 图片懒加载
            2. 接口转发
        优点:
            1. 封装对象调用, 降低系统耦合度
            2. 扩展对象能力, 方便做校验等功能
        缺点:
            1. 非直接访问, 增加开销
            2. 为了增加代理, 产生了额外的工作

        class ProxyImg{
            constructor(id) {
                this.img = document.getElementById(id)
                this.img.src = 'http://www.xxx.com/loading.gif';
                this.tempImg = new Image();
            }
            set src(src) {
                this.tempImg.onload = () => {
                    this.img.src = src
                }
                this.tempImg.src = src
            }
        }

        <script>
            (() => {
                console.log('8. 代理模式');
                class ProxyImg{
                    constructor(id) {
                        this.img = document.getElementById(id)
                        this.img.src = 'http://www.xxx.com/loading.gif';
                        this.tempImg = new Image();
                    }
                    set src(src) {
                        this.tempImg.onload = () => {
                            this.img.src = src
                        }
                        this.tempImg.src = src
                    }
                }
            })()
        </script>
    </pre>

    <h3 id="外观模式">9. 外观模式</h3>
    <pre>
        为子系统定制统一的接口, 属于程序设计范畴

        /pay
            微信支付
            支付宝

        使用场景:
            1. 浏览器兼容处理
            2. 支付接口(一个接口, 根据参数支持 支付宝/微信)

        优点:
            1. 减少系统的相互依赖
            2. 提高灵活性
            3. 提高安全性
        缺点:
            1. 不符合开闭原则，如果要改东西很麻烦，继承重写都不合适

        let aliPay = (money, orderId) => {
            console.log('支付宝: ', money)
        }
    
        let wxPay = (money) => {
            console.log('微信支付: ', money)
        }

        let pay = ({ type, money, orderId }) => {
            if (type === 'aliPay') {
                aliPay(money, orderId)
            } else if (type === 'wxPay') {
                wxPay(money)
            }
        }
        pay({ type: 'aliPay', money: 1024 })
        pay({ type: 'wxPay', money: 2048 })

        <script>
            (() => {
                console.log('9. 外观模式');
                let aliPay = (money) => {
                    console.log('支付宝: ', money)
                }
            
                let wxPay = (money) => {
                    console.log('微信支付: ', money)
                }

                let pay = ({ type, money }) => {
                    if (type === 'aliPay') {
                        aliPay(money)
                    } else if (type === 'wxPay') {
                        wxPay(money)
                    }
                }
                pay({ type: 'aliPay', money: 1024 })
                pay({ type: 'wxPay', money: 2048 })
            })()
        </script>
    </pre>

    <h3 id="桥接模式">10. 桥接模式</h3>
    <pre>
        
        抽象部分与实现部分分离, 他们都可以独自变化

        使用场景: 
            1. 手机的 home 键, 按键的功能随时变化(指纹付款, 回到主屏)

        优点:
            1. 有助于独立管理各个组成部分, 把抽象与实现解耦
            2. 提高可扩展性

        缺点:
            1. 代码量过大, 增加开发成本

        let reLock = {
            touchEnd: () => {
                console.log('手机解锁')
            }
        }
        let goHome = {
            touchEnd: () => {
                console.log('回到主屏')
            }
        }
        let fingerprintPay = {
            touchEnd: () => {
                console.log('指纹支付')
            }
        }

        const homeBtnConfig = {
            lock: reLock,
            app: goHome,
            payWait: fingerprintPay
        }

        class Phone {
            constructor() {
                this.state = 'lock'; //  lock || app | payWait 
                this.homeBtn = homeBtnConfig[this.state]
            }
            setState(state) {
                this.state = state
                this.homeBtn = homeBtnConfig[this.state]
            }
            touchHome() {
                this.homeBtn.touchEnd()
            }
        }

        let p = new Phone()
        p.touchHome()
        p.setState('app')
        p.touchHome()
        p.setState('payWait')
        p.touchHome()


        <script>
            (() => {
                console.log('10. 桥接模式');
                // 手机的例子
                // home 键的三种状态对应的操作
                let reLock = {
                    touchEnd: () => {
                        console.log('手机解锁')
                    }
                }
                let goHome = {
                    touchEnd: () => {
                        console.log('回到主屏')
                    }
                }
                let fingerprintPay = {
                    touchEnd: () => {
                        console.log('指纹支付')
                    }
                }

                const homeBtnConfig = {
                    lock: reLock,
                    app: goHome,
                    payWait: fingerprintPay
                }

                class Phone {
                    constructor() {
                        this.state = 'lock'; //  lock || app | payWait 
                        this.homeBtn = homeBtnConfig[this.state]
                    }
                    setState(state) {
                        this.state = state
                        this.homeBtn = homeBtnConfig[this.state]
                    }
                    touchHome() {
                        this.homeBtn.touchEnd()
                    }
                }

                let p = new Phone()
                p.touchHome()
                p.setState('app')
                p.touchHome()
                p.setState('payWait')
                p.touchHome()

            })()
        </script>
    </pre>

    <h3 id="组合模式">11. 组合模式</h3>
    <pre>
        
        将对象组成树装结构，分出从 “部分-->整体” 的层次。
        每一层都是差不多的结构, 这种类似于"晶体结构"的对象嵌套关系
        可以无限嵌套下去

        使用场景: 
            1. DOM 树
            2. react 虚拟 dom
            3. 区块链

        优点:
            1. 高层模块调用简单
            2. 节点自由增加
        缺点:
            1. 组合模式中设计的对象都是实体类, 而不是接口. 这违反了依赖倒置原则

        let MacroCommand = () => {
            return {
                commandList: [],
                add: function(command) {
                    this.commandList.push(command)
                },
                execute: function() {
                    for (let i = 0, command; command = this.commandList[i]; i++) {
                        command.execute()
                    }
                }
            }
        }

        // 创建对象
        // 工蚁 4
        var antWork_1 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
        var antWork_2 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
        var antWork_3 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
        var antWork_4 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }


        // 兵蚁 2
        var antSoldier_1 = {
            execute: function() { console.log("兵蚁: 指挥交通...") }
        }
        var antSoldier_2 = {
            execute: function() { console.log("兵蚁: 指挥交通...") }
        }
        // 繁殖蚁 2
        var antWoman_1 = {
            execute: function() { console.log("繁殖蚁: 走在大部队前面...") }
        }
        var antWoman_2 = {
            execute: function() { console.log("繁殖蚁: 走在大部队前面...") }
        }

        // 蚁后
        var antQueen = MacroCommand()
        antQueen.add(antWork_1)
        antQueen.add(antWork_2)
        antQueen.add(antWork_3)
        antQueen.add(antWork_4)

        // 命令：搬家
        var moveHome = MacroCommand()
        moveHome.add(antSoldier_1);
        moveHome.add(antSoldier_1);
        moveHome.add(antWoman_1);
        moveHome.add(antWoman_2);
        moveHome.add(antQueen);

        moveHome.execute()

        <script>
            console.log('11. 组合模式');
            (() => {
                let MacroCommand = () => {
                    return {
                        commandList: [],
                        add: function(command) {
                            this.commandList.push(command)
                        },
                        execute: function() {
                            for (let i = 0, command; command = this.commandList[i]; i++) {
                                command.execute()
                            }
                        }
                    }
                }

                // 创建对象
                // 工蚁 4
                var antWork_1 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
                var antWork_2 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
                var antWork_3 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }
                var antWork_4 = { execute: function() { console.log("工蚁：服侍蚁后搬家...") } }


                // 兵蚁 2
                var antSoldier_1 = {
                    execute: function() { console.log("兵蚁: 指挥交通...") }
                }
                var antSoldier_2 = {
                    execute: function() { console.log("兵蚁: 指挥交通...") }
                }
                // 繁殖蚁 2
                var antWoman_1 = {
                    execute: function() { console.log("繁殖蚁: 走在大部队前面...") }
                }
                var antWoman_2 = {
                    execute: function() { console.log("繁殖蚁: 走在大部队前面...") }
                }

                // 蚁后
                var antQueen = MacroCommand()
                antQueen.add(antWork_1)
                antQueen.add(antWork_2)
                antQueen.add(antWork_3)
                antQueen.add(antWork_4)

                // 命令：搬家
                var moveHome = MacroCommand()
                moveHome.add(antSoldier_1);
                moveHome.add(antSoldier_1);
                moveHome.add(antWoman_1);
                moveHome.add(antWoman_2);
                moveHome.add(antQueen);

                moveHome.execute()
            })()
        </script>
    </pre>

    <h3 id="享元模式">12. 享元模式</h3>
    <pre>
        
        某个业务需要大量的相似对象, 此时不必 new 很多对象
        只需要 new 1个对象, 然后不断的改变对象的状态就可以
        替代其他对象完成相应的业务

        使用场景: 
            比如一家服装网店需要拍服装照片男女款各50件。那么他们可以找男女模特各50个，让他们穿上照片来拍照。 但这样太麻烦了，换一种思路。只需要找一男一女两个模特，让他们换50套衣服来拍照就能完美的解决问题了。 这就是一个典型的享元模式的应用场景。
            在程序中构建对象和存储对象都是需要资源的。享元模式可以帮你更好的管理你的对象池。

            享元模式中有两个重要的概念：
                内部状态
            模特的性别就是内部状态，这个字段可以被50件衣服共享
                外部状态
            模特的衣服就是外部状态，每次拍照都要更换衣服。但模特还是那个模特
        优点:
            1. 减少对象的创建, 降低系统内存
        缺点:
            2. 提高了系统的复杂度，需要分离出外部状态和内部状态

        class Model{
            constructor(sex) {
                this.sex = sex;
                this.clothing = ''
            }
            setCothing(v) {
                this.clothing = v
            }
            getPhoto() {
                console.log(`${this.sex} 穿上了 ${this.clothing}`)
            }
        }

        var ManClothingList = [
            '雪中飞2020新款男士短款运动风保暖外套潮可脱卸帽时尚羽绒服反季',
            '莫代尔 长袖t恤男士圆领潮流秋装上衣服纯棉打底衫内搭秋衣',
            '科比裤子男湖人24总冠军联名aj官网詹姆斯23号春季休闲束脚运动裤',
            '优衣库 男装/女装 灯芯绒工装衬衫(水洗产品 长袖) 428955 UNIQLO',
            //  ... 50件
        ]

        var WomanClothingList = [
            '王小鸭2020新款女秋季气质翻领黑色外套短款收腰显瘦大码西装外套',
            'ohsunny防晒衣2020新款长袖夏季薄款防紫外线透气立领百搭防晒服',
            'MOUSSY 秋冬新品个性立领拼接拉链时尚休闲夹克外套024CA130-0001',
            'JoyingFeng9/9 am0:00【英伦学姐】chao气质短款高腰风衣外套',
            '优衣库 女装 双面穿连帽外套 429062 UNIQLO',
            //  ... 50件
        ]

        let manModel = new Model('男')
        let womanModel = new Model('女')
        
        for (let i of ManClothingList) {
            manModel.setCothing(i)
            manModel.getPhoto()
        }

        for (let i of WomanClothingList) {
            womanModel.setCothing(i)
            womanModel.getPhoto()
        }
        <script>
            console.log('12 享元模式');
            (() => {
                class Model{
                    constructor(sex) {
                        this.sex = sex;
                        this.clothing = ''
                    }
                    setCothing(v) {
                        this.clothing = v
                    }
                    getPhoto() {
                        console.log(`${this.sex} 穿上了 ${this.clothing}`)
                    }
                }

                var ManClothingList = [
                    '雪中飞2020新款男士短款运动风保暖外套潮可脱卸帽时尚羽绒服反季',
                    '莫代尔 长袖t恤男士圆领潮流秋装上衣服纯棉打底衫内搭秋衣',
                    '科比裤子男湖人24总冠军联名aj官网詹姆斯23号春季休闲束脚运动裤',
                    '优衣库 男装/女装 灯芯绒工装衬衫(水洗产品 长袖) 428955 UNIQLO',
                    //  ... 50件
                ]

                var WomanClothingList = [
                    '王小鸭2020新款女秋季气质翻领黑色外套短款收腰显瘦大码西装外套',
                    'ohsunny防晒衣2020新款长袖夏季薄款防紫外线透气立领百搭防晒服',
                    'MOUSSY 秋冬新品个性立领拼接拉链时尚休闲夹克外套024CA130-0001',
                    'JoyingFeng9/9 am0:00【英伦学姐】chao气质短款高腰风衣外套',
                    '优衣库 女装 双面穿连帽外套 429062 UNIQLO',
                    //  ... 50件
                ]

                let manModel = new Model('男')
                let womanModel = new Model('女')
                
                for (let i of ManClothingList) {
                    manModel.setCothing(i)
                    manModel.getPhoto()
                }

                for (let i of WomanClothingList) {
                    womanModel.setCothing(i)
                    womanModel.getPhoto()
                }

            })()
        </script>
    </pre>

    <h3 id="观察者模式">13. 观察者模式</h3>
    <pre>
        
        定义一种一对多的关系, 让多个观察者对象同时监听某一个主题对象

        使用场景:
            1. vue 的响应式
            2. 监听上传进度
            3. 游戏下载到某个进度奖励礼包
        优点:
            1. 支持简单的广播通信, 自动通知所有的订阅对象
        缺点:
            1. 过度使用会导致对象与对象之间的联系弱化，会导致程序难以跟踪维护和理解

        class Subject{
            constructor() {
                this.state = 0
                this.observers = []
            }
            getState() {
                return this.state
            }
            setState(state) {
                this.state = state
                this.notifyAllObeservers()
            }
            notifyAllObeservers() {
                this.observers.forEach(observer => {
                    observer.update()
                })
            }
            attach(observer) {
                this.observers.push(observer)
            }
        }

        // 观察者
        class Observer {
            constructor(name, subject) {
                this.name = name
                this.subject = subject
                this.subject.attach(this)
            }
            update() {
                console.log(`${this.name} -进制转换- ${this.subject.state.toString(2)}`)
            }
        }

        let s = new Subject()
        let o1 = new Observer('o1', s)
        let o2 = new Observer('o2', s)
        s.setState(1024)

        <script>
            console.log('13. 观察者模式');
            (() => {
                class Subject{
                    constructor() {
                        this.state = 0
                        this.observers = []
                    }
                    getState() {
                        return this.state
                    }
                    setState(state) {
                        this.state = state
                        this.notifyAllObeservers()
                    }
                    notifyAllObeservers() {
                        this.observers.forEach(observer => {
                            observer.update()
                        })
                    }
                    attach(observer) {
                        this.observers.push(observer)
                    }
                }

                // 观察者
                class Observer {
                    constructor(name, subject) {
                        this.name = name
                        this.subject = subject
                        this.subject.attach(this)
                    }
                    update() {
                        console.log(`${this.name} -进制转换- ${this.subject.state.toString(2)}`)
                    }
                }

                let s = new Subject()
                let o1 = new Observer('o1', s)
                let o2 = new Observer('o2', s)
                s.setState(1024)

            })()
        </script>
    </pre>

    <h3 id="订阅发布模式">14. 订阅发布模式</h3>
    <pre>
        
        使用场景: 
            1. 夸组件通信
        优点:   
            1. 时间解耦
            2. 对象间解耦 在异步编程中有大量的适应场景。
        缺点:
            1. 如果大量使用的话, 程序逻辑非常复杂难以理解

        class Event {
            constructor() {
                this.map = {}
            }
            //监听
            on(key, fn) { 
                let fns = this.map[key]
                if (Array.isArray(fns)) {
                    fns.push(fn)
                } else {
                    this.map[key] = [fn]
                }
            }
            // 触发
            emit(key, ...args) {
                let fns = this.map[key]
                if (Array.isArray(fns)) {
                    fns.forEach(fnItem => fnItem(args))
                }
            }
            // 移除
            remove(key, fn) {
                let fns = this.map[key]
                if (Array.isArray(fns)) {
                    let index = fns.findIndex(fnItem => fnItem === fn)
                    if (index !== -1) {
                        fns.splice(index, 1)
                    }
                }
            }
        }

        let event = new Event()
        
        let fn1 = () => {
            console.log('fn1');
        }
        let fn2 = () => {
            console.log('fn2');
        }

        event.on('x', fn1)
        event.on('x', fn2)
        event.remove('x', fn1)
        event.emit('x')

        <script>
            (() => {
                console.log('14. 订阅发布模式');

                class Event {
                    constructor() {
                        this.map = {}
                    }
                    //监听
                    on(key, fn) { 
                        let fns = this.map[key]
                        if (Array.isArray(fns)) {
                            fns.push(fn)
                        } else {
                            this.map[key] = [fn]
                        }
                    }
                    // 触发
                    emit(key, ...args) {
                        let fns = this.map[key]
                        if (Array.isArray(fns)) {
                            fns.forEach(fnItem => fnItem(args))
                        }
                    }
                    // 移除
                    remove(key, fn) {
                        let fns = this.map[key]
                        if (Array.isArray(fns)) {
                            let index = fns.findIndex(fnItem => fnItem === fn)
                            if (index !== -1) {
                                fns.splice(index, 1)
                            }
                        }
                    }
                }

                let event = new Event()
                
                let fn1 = () => {
                    console.log('fn1');
                }
                let fn2 = () => {
                    console.log('fn2');
                }

                event.on('x', fn1)
                event.on('x', fn2)

                event.remove('x', fn1)
                

                event.emit('x')

            })()
        </script>
    </pre>

    <h3 id="迭代器模式">15. 迭代器模式</h3>
    <pre>
        
        提供一个顺序访问某个聚合对象的每个元素的方法
        同时还不暴露对象内部结构(单一职责原则)

        使用场景: 
            1. 内部迭代器: Array.forEach
            2. 外部迭代器: ES6 中的 generator 必须调用 .next() 才会继续往下执行
        优点:
            1. 它支持一不同的方式遍历一个聚合对象
            2. 迭代器简化了聚合类
        缺点:
            1. 相应的类要单独维护迭代相关的代码

        // 定义数组
        var a = [11,22,3]
        // 内部迭代器
        a.each = function(fn) {
            for (let i = 0; i < this.length; i++) {
                fn(a[i], this)
            }
        }
        // 调用内部迭代器
        a.each(function(i, list) {
            console.log(i, list);
        });

        console.log('外部迭代器');
        // 外部迭代器
        class Iterator {
            constructor(arr) {
                this.current = 0
                this.arr = arr
            }
            next() {
                this.current += 1
            }
            isDone() {
                return this.current <= this.arr.length - 1
            }
            getCurrentItem() {
                return this.arr[this.current]
            }
        }

        // 测试代码
        let i_1 = new Iterator([11, 22, 33])
        
        while(i_1.isDone()){
            console.log(i_1.getCurrentItem());
            i_1.next()
        }

        <script>
            (() => {
                console.log('15. 迭代器模式');
                // 定义数组
                var a = [11,22,3]
                // 内部迭代器
                a.each = function(fn) {
                    for (let i = 0; i < this.length; i++) {
                        fn(a[i], this)
                    }
                }
                // 调用内部迭代器
                a.each(function(i, list) {
                    console.log(i, list);
                });

                console.log('外部迭代器');
                // 外部迭代器
                class Iterator {
                    constructor(arr) {
                        this.current = 0
                        this.arr = arr
                    }
                    next() {
                        this.current += 1
                    }
                    isDone() {
                        return this.current <= this.arr.length - 1
                    }
                    getCurrentItem() {
                        return this.arr[this.current]
                    }
                }

                // 测试代码
                let i_1 = new Iterator([11, 22, 33])
                
                while(i_1.isDone()){
                    console.log(i_1.getCurrentItem());
                    i_1.next()
                }
            })()
        </script>
    </pre>

    <h3 id="策略模式">16. 策略模式</h3>
    <pre>

        定义一系列算法, 把它们封装起来. 并且可以相互替换
        
        使用场景: 
            1. 表单验证, 每个字段都有多种验证方法. 策略模式可以复用这些验证方法
        优点:
            1. 算法可以自由切换
            2. 避免使用多重条件判断
            3. 扩展性好
        缺点:
            1. 代码比较分散, 逻辑多的时候 不好维护

        const strategies = {
            isNonEmpty: function(val, msg) {
                if (!val) {
                    return msg
                }
            },
            minLength: function(val, msg, length) {
                if (val.length < length) {
                    return msg
                }
            },
            isMobile: function(val, msg) {
                var res = !/(^1[3|5|8][0-9]{9}$)/.test(val)
                if (res) {
                    return msg
                }
            }
        }
        // 校验对象
        class Validateor {
            constructor() {
                this.cache = []
            }
            add(val, rules) {
                /*
                    rules: { action, errMsg, length }
                */
                for (let i = 0, rule; rule = rules[i]; i++) {
                    const { action, errMsg, length } = rule
                    this.cache.push(() => {
                        return strategies[action](val, errMsg, length)
                    })
                }
            }
            start() {
                for (let fn of this.cache) {
                    let result = fn()
                    if (result) {
                        return result
                    }
                }
                return true
            }
        }

        let validateor = new Validateor()
        /*
            用户名
            密码
            手机号码
        */
        // 用户名
        validateor.add('xinglong.shi', [
            {
                action: 'isNonEmpty', errMsg: '用户名不能为空', 
            },
            {
                action: 'minLength', errMsg: '用户名的长度不能小于8位', length: 8
            },
        ])

        validateor.add('1234567891111', [
            {
                action: 'minLength', errMsg: '密码的长度不能小于12位',  length: 12
            },
        ])

        validateor.add('15021111111', [
            {
                action: 'isMobile', errMsg: '请输入正确的手机号码',
            },
        ])

        console.log(
            validateor.start()
        )

        <script>
            (() => {
                console.log('16. 策略模式');
                // 策略对象
                const strategies = {
                    isNonEmpty: function(val, msg) {
                        if (!val) {
                            return msg
                        }
                    },
                    minLength: function(val, msg, length) {
                        if (val.length < length) {
                            return msg
                        }
                    },
                    isMobile: function(val, msg) {
                        var res = !/(^1[3|5|8][0-9]{9}$)/.test(val)
                        if (res) {
                            return msg
                        }
                    }
                }
                // 校验对象
                class Validateor {
                    constructor() {
                        this.cache = []
                    }
                    add(val, rules) {
                        /*
                            rules: { action, errMsg, length }
                        */
                        for (let i = 0, rule; rule = rules[i]; i++) {
                            const { action, errMsg, length } = rule
                            this.cache.push(() => {
                                return strategies[action](val, errMsg, length)
                            })
                        }
                    }
                    start() {
                        for (let fn of this.cache) {
                            let result = fn()
                            if (result) {
                                return result
                            }
                        }
                        return true
                    }
                }

                let validateor = new Validateor()
                /*
                    用户名
                    密码
                    手机号码
                */
                // 用户名
                validateor.add('xinglong.shi', [
                    {
                        action: 'isNonEmpty', errMsg: '用户名不能为空', 
                    },
                    {
                        action: 'minLength', errMsg: '用户名的长度不能小于8位', length: 8
                    },
                ])

                validateor.add('1234567891111', [
                    {
                        action: 'minLength', errMsg: '密码的长度不能小于12位',  length: 12
                    },
                ])

                validateor.add('15021111111', [
                    {
                        action: 'isMobile', errMsg: '请输入正确的手机号码',
                    },
                ])

                console.log(
                    validateor.start()
                )
            })()
        </script>
    </pre>

    <h3 id="模板方法模式">17. 模板方法模式</h3>
    <pre>
        
        父类, 或者抽象类. 按照一定的顺序调用函数
        表示所有的子类都要按照这个顺序执行

        使用场景: 
            1. 不管是笔记本还是台式机, 开机是顺序都是 
                1. 先按下开机键, 
                2. 然后电源通电
                3. 主板通电
                4. cpu, 硬盘开始工作
                5. 显示器亮起
        优点:
            1. 封装不变的部分, 扩展可变的部分
            2. 提取公共代码，便于维护
            3. 行为由父类控制，子类实现。
        缺点:
            1. 增加了系统复杂度，主要是增加了的抽象类和类间联系

        class Computer {
            constructor() {
                this.name = 'Computer'
            }
            customerWantsCondiments() {
                return false
            }
            init() { //模板方法
                console.log(this.name + '准备开机')
                this.installBattery() // 电源通电
                this.installMonitor() // 显示器亮起
                if(this.customerWantsCondiments()) { // 模板方法钩子
                    this.addCondiments()
                }
                console.log('开机成功');
            }
        }

        // 台式机 联想
        class Lianxiang extends Computer{
            constructor() {
                super()
                this.name = '联想'
            }
            installBattery() {
                console.log(this.name + '电源通电')
            }
            installMonitor() {
                console.log(this.name + '显示器亮起')
            }
        }

        // 笔记本 mac
        class Mac  extends Computer{
            constructor() {
                super()
                this.name = 'Mac'
            }
            installBattery() {
                console.log(this.name + '电源通电')
            }
            installMonitor() {
                console.log(this.name + '显示器亮起')
            }
            customerWantsCondiments() {
                return true
            }
            addCondiments() {
                console.log(this.name + '打开用户登录界面')
            }
        }

        new Lianxiang().init()
        new Mac().init()

        <script>
            (() => {
                console.log('17. 模板方法模式');
                class Computer {
                    constructor() {
                        this.name = 'Computer'
                    }
                    customerWantsCondiments() {
                        return false
                    }
                    init() { //模板方法
                        console.log(this.name + '准备开机')
                        this.installBattery() // 电源通电
                        this.installMonitor() // 显示器亮起
                        if(this.customerWantsCondiments()) { // 模板方法钩子
                            this.addCondiments()
                        }
                        console.log('开机成功');
                    }
                }

                // 台式机 联想
                class Lianxiang extends Computer{
                    constructor() {
                        super()
                        this.name = '联想'
                    }
                    installBattery() {
                        console.log(this.name + '电源通电')
                    }
                    installMonitor() {
                        console.log(this.name + '显示器亮起')
                    }
                }

                // 笔记本 mac
                class Mac  extends Computer{
                    constructor() {
                        super()
                        this.name = 'Mac'
                    }
                    installBattery() {
                        console.log(this.name + '电源通电')
                    }
                    installMonitor() {
                        console.log(this.name + '显示器亮起')
                    }
                    customerWantsCondiments() {
                        return true
                    }
                    addCondiments() {
                        console.log(this.name + '打开用户登录界面')
                    }
                }

                new Lianxiang().init()
                new Mac().init()
            })()
        </script>
    </pre>

    <h3 id="职责链模式">18. 职责链模式</h3>
    <pre>
        多个对象都有计划处理到请求, 类似于 koa 中的洋葱模型, 或者 axios 中的拦截器

        使用场景:
            1. (生活例子)快递没到一个站点都要中转
            2. 请假审批
        优点:
            1. 降低耦合度。它将请求的发送者和接收者解耦。
            2. 简化逻辑结构, 且结构可以随意搭配
        缺点:
            1. 不能保证每个请求都会被链中的节点处理(koa 中必须调用 next())
            2. 整个链条中往往只有几个节点起到关键作用, 过长的职责链会带来性能损耗

        class Action {
            constructor(name) {
                this.name = name
                this.nextAction = null
            }
            setNext(action) {
                this.nextAction = action
            }
            handle() {
                console.log(`${this.name} 审批`)
                if (this.nextAction !== null) {
                    this.nextAction.handle()
                }
            }
        }

        let a1 = new Action('组长')
        let a2 = new Action('经理')
        let a3 = new Action('总监')

        a1.setNext(a2)
        a2.setNext(a3)
        a1.handle()

        <script>
            (() => {
                console.log('18. 职责链模式');
                class Action {
                    constructor(name) {
                        this.name = name
                        this.nextAction = null
                    }
                    setNext(action) {
                        this.nextAction = action
                    }
                    handle() {
                        console.log(`${this.name} 审批`)
                        if (this.nextAction !== null) {
                            this.nextAction.handle()
                        }
                    }
                }

                let a1 = new Action('组长')
                let a2 = new Action('经理')
                let a3 = new Action('总监')

                a1.setNext(a2)
                a2.setNext(a3)
                a1.handle()
            })()
        </script>
    </pre>

    <h3 id="命令模式">19. 命令模式</h3>
    <pre>
        
        命令模式有三个角色：调用者，接受者，执行者
        
        将一个请求封装为一个命令对象。
        调用者 不需要知道 执行者 是谁，因为有 接受者 会帮你挑选好 执行者。

        使用场景: 
            1. 网页的富文本编辑器
        优点:
            1. 对命令进行封装，使命令易于扩展和修改
            2. 命令发出者和接受者解耦，使发出者不需要知道命令的具体执行过程即可执行
        缺点:
            1. 使用命令模式可能会导致某些系统有过多的具体命令类。

        // 执行者
        class Receiver {
            execute() {
                console.log('执行请求')
            }
        }
        // 接受者 - 命令对象
        class Command {
            constructor(receiver) {
                this.receiver = receiver
            }
            execute() {
                console.log(`接受请求`);
                this.receiver.execute()
            }
        }
        // 调用者
        class Invoker{
            constructor(command) {
                this.command = command
            }
            invoke() {
                console.log(`发送请求`);
                this.command.execute()
            }
        }
        // 仓库
        // 订单
        // 客户
        const wareHouse = new Receiver()
        const order = new Command(wareHouse)
        const client = new Invoker(order)
        client.invoke()

        <script>
            (() => {
                console.log('19. 命令模式');
                // 执行者
                class Receiver {
                    execute() {
                        console.log('执行请求')
                    }
                }
                // 接受者 - 命令对象
                class Command {
                    constructor(receiver) {
                        this.receiver = receiver
                    }
                    execute() {
                        console.log(`接受请求`);
                        this.receiver.execute()
                    }
                }
                // 调用者
                class Invoker{
                    constructor(command) {
                        this.command = command
                    }
                    invoke() {
                        console.log(`发送请求`);
                        this.command.execute()
                    }
                }
                // 仓库
                // 订单
                // 客户
                const wareHouse = new Receiver()
                const order = new Command(wareHouse)
                const client = new Invoker(order)
                client.invoke()
            })()
        </script>
    </pre>

    <h3 id="备忘录模式">20. 备忘录模式</h3>
    <pre>
        
        不破坏封装的前提下, 提供一个获取内部状态的函数, 并在其他地方保存这个状态
        这样以后可以方便的恢复到这个状态

        使用场景: 
            1. 分页控件
            2. 撤销功能
        优点:
            1. 给用户提供了一种可以恢复状态的机制，可以使用户能够比较方便地回到某个历史的状态
        缺点:
            1. 资源消耗, 如果不合理使用资源. 将会占用过多的内存

        // 备忘录
        class Memento {
            constructor(content) {
                this.content = content
            }
            getContent() {
                return this.content
            }
        }
        // 备忘录列表
        class CareTaker{
            constructor() {
                this.list = []
            }
            add(memento) {
                this.list.push(memento)
            }
            get(index) {
                return this.list[index]
            }
        }

        // 编辑器
        class Editor{
            constructor() {
                this.content = null
            }
            setContent(content) {
                this.content = content
            }
            getContent() {
                return this.content
            }
            saveContentToMemento() {
                return new Memento(this.content)
            }
            getContentFromMemento(memento){
                this.content = memento.getContent()
            }
        }
        //测试代码

        const editor = new Editor()
        const careTaker = new CareTaker()

        editor.setContent('111')
        editor.setContent('222')
        careTaker.add(editor.saveContentToMemento())
        editor.setContent('333')
        careTaker.add(editor.saveContentToMemento())
        editor.setContent('444')

        console.log(
            editor.getContent()
        )
        editor.getContentFromMemento(careTaker.get(1))

        console.log(
            editor.getContent() // 333
        )

        editor.getContentFromMemento(careTaker.get(0))

        console.log(
            editor.getContent() // 222
        )
        <script>
            (() => {
                console.log('20. 备忘录模式')
                // 备忘录
                class Memento {
                    constructor(content) {
                        this.content = content
                    }
                    getContent() {
                        return this.content
                    }
                }
                // 备忘录列表
                class CareTaker{
                    constructor() {
                        this.list = []
                    }
                    add(memento) {
                        this.list.push(memento)
                    }
                    get(index) {
                        return this.list[index]
                    }
                }

                // 编辑器
                class Editor{
                    constructor() {
                        this.content = null
                    }
                    setContent(content) {
                        this.content = content
                    }
                    getContent() {
                        return this.content
                    }
                    saveContentToMemento() {
                        return new Memento(this.content)
                    }
                    getContentFromMemento(memento){
                        this.content = memento.getContent()
                    }
                }
                //测试代码

                const editor = new Editor()
                const careTaker = new CareTaker()

                editor.setContent('111')
                editor.setContent('222')
                careTaker.add(editor.saveContentToMemento())
                editor.setContent('333')
                careTaker.add(editor.saveContentToMemento())
                editor.setContent('444')

                console.log(
                    editor.getContent()
                )
                editor.getContentFromMemento(careTaker.get(1))

                console.log(
                    editor.getContent() // 333
                )

                editor.getContentFromMemento(careTaker.get(0))

                console.log(
                    editor.getContent() // 222
                )

            })()
        </script>
    </pre>

    <h3 id="状态模式">21. 状态模式</h3>
    <pre>
        一个对象的状态改变时, 那么他的行为也会发生改变. 
        比如一个大学生毕业了就必须找工作
        一个女生到了试婚年龄就必须结婚

        使用场景: 
            1. 一个台灯的开关: 状态 （弱光、强光、关灯）
            2. 订单状态: 未付款, 已付款, 已发货, 发起退款
        优点:
            1. 状态与行为的关系封装在一个类里, 方便更改
            2. 状态与状态间，行为与行为间彼此独立互不干扰
        缺点:
            1. 会在系统中定义许多状态类
            2. 逻辑分散

        <script>
            (() => {
                console.log('21. 状态模式')
                // 台灯: 状态(弱光, 强光, 关灯)
                class State {
                    constructor(state) {
                        this.state = state
                    }
                    handle(context) {
                        console.log(`this is ${this.state} light`)
                        context.setState(this)
                    }
                }

                class Lamp{ // 灯
                    constructor() {
                        this.state = null
                    }
                    getState() {
                        return this.state
                    }
                    setState(state) {
                        this.state = state
                    }
                }

                let lamp = new Lamp()
                let weak = new State('weak') // 弱光
                let strong = new State('strong') // 强光
                let off = new State('off') // 关闭

                weak.handle(lamp)
                console.log(lamp.getState());

                strong.handle(lamp)
                console.log(lamp.getState());

                off.handle(lamp)
                console.log(lamp.getState());

            })()
        </script>
    </pre>

    <h3 id="访问者模式">22. 访问者模式</h3>
    <pre>
        想要访问一个对象的内部, 但又不能修改原本的类

        使用场景: 
            1. 对象结构中对象对应的类很少改变，但经常需要在此对象结构上定义新的操作
        优点:
            1. 符合单一职责原则。
            2、优秀的扩展性。 
            3、灵活性。
        缺点:
            1. 具体元素变更比较困难
            2. 违反了依赖倒置原则，依赖了具体类，没有依赖抽象

            // 访问者
            class Visitor {
                visitConcreteElement(concreteElement) {
                    concreteElement.operation()
                    concreteElement.doAction()
                }
            }

            // 元素类
            class ConcreteElement {
                doAction() {
                    console.log('doAction');
                }
                operation() {
                    console.log('operation')
                }
                accept(visitor) {
                    visitor.visitConcreteElement(this)
                }
            }

            // 测试
            let visitor = new Visitor()
            let element = new ConcreteElement()
            element.accept(visitor)

        <script>
            (() => {
                console.log('22. 访问者模式');

                // 访问者
                class Visitor {
                    visitConcreteElement(concreteElement) {
                        concreteElement.operation()
                        concreteElement.doAction()
                    }
                }

                // 元素类
                class ConcreteElement {
                    doAction() {
                        console.log('doAction');
                    }
                    operation() {
                        console.log('operation')
                    }
                    accept(visitor) {
                        visitor.visitConcreteElement(this)
                    }
                }

                // 测试
                let visitor = new Visitor()
                let element = new ConcreteElement()
                element.accept(visitor)

            })()
        </script>
    </pre>

    <h3 id="中介者模式">23. 中介者模式</h3>
    <pre>
        
        用一个中间对象来封装一系列对象的交互，对象间不需要互相引用。
        从而实现松耦合，而且还可以独立的改变他们之间的交互

        使用场景: 
            机场塔台用来调度飞机的起飞和降落，塔台就是中介者。
            如果没有塔台各个机长之间相互协调将是一个非常繁杂的工作，
            每一个飞机的起飞和降落都要通知其他的飞机这是非常繁琐的。

        优点:
            1. 降低了类的复杂度，将一对多转化成了一对一
            2. 各个类之间的解耦
        缺点:
            1. 中介者会庞大，变得复杂难以维护


        class Control{
            constructor() {
                this.all = {}
            }
            register(plane) {
                this.all[plane.name] = plane
            }
            send(msg, to) {
                this.all[to.name].receive(`Control: ${to.name} 接受消息 ${msg}`)
            }
        }
        
        // 飞机
        class Plane{
            constructor(name, control) {
                this.name = name
                this.control = control
            }
            send(msg, to) {
                this.control.send(msg, to)
            }
            receive(msg) {
                console.log(msg)
            }
        }
        let control = new Control()
        const p1 = new Plane('p1', control)
        const p2 = new Plane('p2', control)

        control.register(p1)
        control.register(p2)

        p1.send('我马上降落', p2)
        p2.send('我收到了', p1)

        <script>
            console.log('23. 中介者模式');
            (() => {
                // 塔台, 飞机
                class Control{
                    constructor() {
                        this.all = {}
                    }
                    register(plane) {
                        this.all[plane.name] = plane
                    }
                    send(msg, to) {
                        this.all[to.name].receive(`Control: ${to.name} 接受消息 ${msg}`)
                    }
                }
                
                // 飞机
                class Plane{
                    constructor(name, control) {
                        this.name = name
                        this.control = control
                    }
                    send(msg, to) {
                        this.control.send(msg, to)
                    }
                    receive(msg) {
                        console.log(msg)
                    }
                }
                let control = new Control()
                const p1 = new Plane('p1', control)
                const p2 = new Plane('p2', control)

                control.register(p1)
                control.register(p2)

                p1.send('我马上降落', p2)
                p2.send('我收到了', p1)

            })()
        </script>
    </pre>

    <h3 id="解释器模式">24. 解释器模式</h3>
    <pre>

        定义一个语言的文法, 并且建立一个解释器来解释该语言中的句子
        一般用于专业场景, 涉及到编译原理

        使用场景: 
            babel, less
        优点:
            1. 易于改变和扩展文法。
            2. 由于在解释器模式中使用类来表示语言的文法规则，因此可以通过继承等机制来改变或扩展文法
        缺点:
            1. 执行效率较低，在解释器模式中使用了大量的循环和递归调用，
                因此在解释较为复杂的句子时其速度慢
            2. 对于复杂的文法比较难维护

        class Context {
            constructor() {
                this._list = []
                this._sum = 0
            }
            get sum() {
                return this._sum
            }
            set sum(val) {
                this._sum = val
            }
            add(expression) {
                this._list.push(expression)
            }
            get list() {
                return [...this._list]
            }

            doInterpret() {
                for (const expression of this._list) {
                    expression.interpret(this)
                }
            }
        }

        class PlusExpression{
            interpret(context) {
                context.sum += 1
            }
        }

        class MinusExpression {
            interpret(context) {
                context.sum -= 1
            }
        }

        const context = new Context()
        context.add(new PlusExpression())
        context.add(new PlusExpression())
        context.add(new PlusExpression())
        context.add(new MinusExpression())

        context.doInterpret()

        console.log(
            context.sum
        )
        <script>
            console.log('24. 解释器模式');
            (() => {
                class Context {
                    constructor() {
                        this._list = []
                        this._sum = 0
                    }
                    get sum() {
                        return this._sum
                    }
                    set sum(val) {
                        this._sum = val
                    }
                    add(expression) {
                        this._list.push(expression)
                    }
                    get list() {
                        return [...this._list]
                    }

                    doInterpret() {
                        for (const expression of this._list) {
                            expression.interpret(this)
                        }
                    }
                }

                class PlusExpression{
                    interpret(context) {
                        context.sum += 1
                    }
                }

                class MinusExpression {
                    interpret(context) {
                        context.sum -= 1
                    }
                }

                const context = new Context()
                context.add(new PlusExpression())
                context.add(new PlusExpression())
                context.add(new PlusExpression())
                context.add(new MinusExpression())

                context.doInterpret()

                console.log(
                    context.sum
                )
                // less, scss, es6
            })()
        </script>
    </pre>

</body>
</html>
